<?PHP

/*
Plugin Name: inlineRSS
Plugin URI: http://www.iconophobia.com/
Description: Allows inline RSS feeds from any source in any format (hopefully).  Click <a href="../wp-admin/templates.php?file=%2Fwp-content%2Fplugins%2FinlineRSS.txt">here</a> to edit the sources and <a href="../wp-admin/templates.php?file=%2Fwp-content%2Fplugins%2FinlineRSS.xslt">here</a> to edit the xslt file. <em>Usage in templates: <code>inlineRSS('feedname');</code>  Usage in posts / pages <code>!inlineRSS:feedname</code></em>.  This flavour uses multiple processing engines.
Version: 1.1
Author: Cal Demaine
Author URI: http://www.iconophobia.com/
*/

// This is a generic PHP function to pull an RSS feed and return an HTML-formatted string
// The $name is the casual name used in the inlineRSS.txt configuration file, not the
// actual feed URL.  Because HTML is not processed, this can also grab raw HTML files.
// Requests are buffered for a user defined number of minutes on the server, to avoid pounding
// the snot out of the RSS feed source.  The default is 30 minutes.

// Version 0.9  Adds PHP 5, DOM XML, and CURL support
// Version 0.91 Adds debug mode, user-specified XSLT files, Hail-Mary file reads when feed fails
// Version 0.92 Fixes the bugs I added in 0.91, particularly around new feeds
// Version 0.93 Fixes PHP xslt sablotron support, broken in 0.9
// Version 0.94 Checks for xslt-specific function of DOMXML, since some users have DOM without
//              xslt
// Version 0.95 Added a few more DIEs for status reporting, fixed comment processing on blank
//              lines.  Checks to see if it can write to a file before doing it.
// Version 1.0  Fixed get_file_contents branch, added "skipxslt" and "eval" functions, although
//              it's unclear what you'd use these for.
// Version 1.1  Added a second function to call inlineRSS with all variables, with the hopes of
//              being able to provide a better user interface experience.  Yeah, like we care
//              about the users. 

function inlineRSSparser($name,$url = "", $timeout = 30, $translator = "") {
  $found = FALSE;                  // Feed was found
  $debug = FALSE;                  // Debug mode, for extra info
  $path = 'wp-content/plugins/';   // Where to look for all config files
  $paramfile = 'inlineRSS.conf';   // Configuration file of feeds
  $fileprefix = 'inlineRSS/in_';             // What feed casual names get prefixed with
  $xsltfile = 'inlineRSS.xslt';    // The default xsltfile
  $maxage = 60 * 60 * 24;          // Maximum file age is 1 day before an error is thrown
  $prefixedwarning = "";           // A warning that can be tacked onto the returned feed

  // If we have no URL, we're going to have to go looking in the parameter file

  if ($url == "") {

    $parameters = file(ABSPATH. $path . $paramfile) or die("Cannot read parameter file " . $paramfile);
  
    foreach ($parameters as $line) {
  
      $line = trim($line);
    
      // Keywords such as DEBUG and # must come first or they'll never get processed.
    
      if (strtoupper($line) == "DEBUG") {  // In case we ever add a better debug
        $debug = TRUE;
        continue;
      }
    
      if (ereg("^[:space:]*#",$line)) {  // Comment, don't bother processing.  It's just style.
        continue;
      }
    
      $casualname = "";
      $feedURL = "";
      $updatetime = "";
      $alternateXSLT = "";   
        
      list($casualname, $feedURL, $updatetime, $alternateXSLT) = explode(",", $line . ",,,,");
      if (strtoupper($name) == strtoupper($casualname)) {
        $found = TRUE;
        break;                             
      }
    } 

  
  } else {  // Let's bypass the whole configuration file business and get to the action
  
    $casualname = $name;
    $feedURL  = $url;
    $updatetime = $timeout;
    $alternateXSLT = $translator;
    $found = TRUE;
    
  }


  if ($name != "" && $found == TRUE) { // This means we've got something to process           
    if ($updatetime == 0) {
      $updatetime = 30;                 
    }    
 
    $casualpathname = ABSPATH . "wp-content/plugins/" . $fileprefix . $casualname . ".xml";

    if ( file_exists($casualpathname)) {
      // We have a local copy, get it just in case
      $xml = file_get_contents($casualpathname);
      // And check its age
      $age = time() - filectime($casualpathname);
      $exists = TRUE;  
    } else {
      $age = 0;
      $exists = FALSE;
      $xml = "<!-- No valid XML -->";
    }
  
  
    if ( $exists == FALSE or $age > $updatetime * 60 ) {  // If there's no file, or it's old
          
      // revised to now use CURL because, well, it's a tiny bit safer.
      
      if (function_exists('curl_init') and stristr($feedURL,"http:")) {
        $curl_handle=curl_init();
        curl_setopt($curl_handle,CURLOPT_URL,$feedURL);
        curl_setopt($curl_handle,CURLOPT_CONNECTTIMEOUT,10);
        curl_setopt($curl_handle,CURLOPT_RETURNTRANSFER,1);
        $curlxml = curl_exec($curl_handle);
        curl_close($curl_handle);

        if (empty($curlxml))  {
          if ($age > $maxage or $exists == FALSE) {
            die("Error reading feed $casualname using curl.");
           }
          $writefile = FALSE;
        } else {
          $xml = $curlxml;
          $writefile = TRUE;
        }
            
      } else {
        //  If CURL is giving you problems, use the line below instead.
        $filegetxml = file_get_contents($feedURL);
        if (empty($filegetxml)) {
          if ($age > $maxage or $exists == FALSE) {
            die("Error reading feed $casualname using file_get.");
          } 
          $writefile = FALSE;
        } else {
          $xml = $filegetxml;
          $writefile = TRUE;
        }
      } 
          
      if ($writefile) {
        if (!($handle = fopen($casualpathname,'w'))) die ("Error opening $casualpathname - possible permissions issue - directory permissions are " . substr(sprintf('%o', fileperms(ABSPATH . "wp-content/")), -4));
        fwrite($handle,$xml);
        fclose($handle);
      }
    }
    
    if (strlen($xml) < 1) die ("XML feed $casualname is empty.\n");
    
    switch (strtoupper($alternateXSLT)) {
      case "SKIPXSLT":  // Skip the XSLT processing, just bring in the file
        $xslt_result = $xml;
        break;
      
      case "EVAL":  // Skip XSLT processing and execute the file.  Very dangerous!
        $xslt_result = eval($xml);
        break;
      
      default:  // Proceed with XSLT processing.
        
        if ( !empty($alternateXSLT) ) {  // Use the alternate XSLT file if specified
          $xsltfile = $alternateXSLT;
        }
              
        $xsl = file_get_contents(ABSPATH. $path . $xsltfile) or die("Error reading XSL file " . $xsltfile);
        
        // This is a switchboard to choose the proper XSLT processing engine and grind through it.
        
        if (PHP_VERSION >= 5) {
          $xslt = new xsltProcessor;
          $xslt->importStyleSheet(DomDocument::loadXML($xsl));
          $xslt_result = $xslt->transformToXML(DomDocument::loadXML($xml));   
           
        } elseif (function_exists('domxml_open_mem') && function_exists('domxml_xslt_stylesheet')) {  // PHP 4 DOM_XML support
          if (!$domXml = domxml_open_mem($xml)) {
            $result = "Error while parsing the xml document\n";
          }
    
          $domXsltObj = domxml_xslt_stylesheet( $xsl );
          $domTranObj = $domXsltObj->process( $domXml );
          $xslt_result = $domXsltObj->result_dump_mem( $domTranObj );
    
    
        } elseif (function_exists('xslt_create')) {  // PHP 4 XSLT library
          $arguments = array (
            '/_xml' => $xml,
            '/_xsl' => $xsl
          );
    
          $xslt_inst = xslt_create();    
          $xslt_result = xslt_process($xslt_inst,'arg:/_xml','arg:/_xsl', NULL, $arguments);
          xslt_free($xslt_inst);
          
        } else {  // Nothing, no valid processor found.  Curses.
          die("No valid XSLT processor found");     
        }
        
        if (empty($xslt_result)) {
          $xslt_result = "Horrific XSLT error - $casualname returned empty - verify $casualpathname is a valid XML file and check logs.\n";
        }   
      
      // Yes, there is a skipped indent here to make the Case statement look nice.
    
    } // End of Switch
    
    $result = "<!-- inlineRSS - beginning of $casualname feed -->\n" . $prefixedwarning . $xslt_result . "\n<!-- end of $name feed -->";

  } else {
    $result = "Feed $name not found\n";
  }
  
  return $result;  
}

//
// This is a Wordpress content filter that replaces calls to inlineRSS
// Any entries like !inlineRSS:iconophobia will be replaced with the
// inlineRSS feed identified by "iconophobia".
//

function inlineRSScontent ($content = '') {

  $find[] = "//";  
  $replace[] = "";

  preg_match_all('/!inlineRSS:(\w+)/', $content, $matches, PREG_SET_ORDER);

  foreach ($matches as $val) {
    $find[] = "/" . $val[0] . "/";
    $replace[] = inlineRSSparser($val[1]);
  }

  return preg_replace($find, $replace, $content);

}

// This is for the php template-calling support:

function inlineRSS($name,$url = "", $timeout = 30, $translator = "") {
  echo(inlineRSSparser($name, $url, $timeout, $translator));
}

// This adds the filter to Wordpress.

add_filter('the_content', 'inlineRSScontent');

?>
